/*
 * Project: Sudoku Explainer
 * Copyright (C) 2006-2007 Nicolas Juillerat
 * 
 * Project: Sudoku Hint Engine for Android
 * Copyright (C) 2010-2013 Emanuel Moecklin
 * 
 * Available under the terms of the Lesser General Public License (LGPL)
 */
package com.onegravity.sudoku.solver.core;

import java.io.Serializable;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;

import com.onegravity.sudoku.solver.HintType;
import com.onegravity.sudoku.solver.data.Cell;
import com.onegravity.sudoku.solver.data.Grid;
import com.onegravity.sudoku.solver.data.ImmutableCell;
import com.onegravity.sudoku.solver.data.Region;
import com.onegravity.sudoku.solver.util.Link;

/**
 * Abstract class for a hint.
 * <p>
 * A hint is (usually) capable of advancing one step in the solving
 * process of a sudoku. Warnings and info messages are also implemented
 * as hints, see {@link WarningHint} subclass.
 * @see DirectHint
 * @see IndirectHint
 */
public abstract class Hint implements Serializable {

	private static final long serialVersionUID = 3490871534281467404L;

	private long sortKey = 0;
	
	/**
	 * These variables are used to keep track of how the hint was used
	 * The purpose of this is to be able to add time to the game timer when the hint is used the first time
	 */
	private int hintUsageSmallClue = 0;
	private int hintUsageBigClue = 0;
	private int hintUsageHint = 0;
	
    /**
     * Apply this hint on the current Sudoku grid.
     */
    public abstract Grid apply();

    /**
     * Get the cell that can be filled, if any, by applying this hint
     * @return the cell that can be filled, or <tt>null</tt> if none exists
     */
    public abstract Cell getCell(HintType hintType);

    /**
     * Get the value that can be placed in the cell if any (1..9, 0 --> no value to set).
     * @return the value that can be placed in the cell
     * @see #getCell()
     */
    public abstract int getValue();

    /**
     * Get the cells that must be highlighted in a visual representation of this hint.
     * @return the cells that must be highlighted in a visual representation, or <tt>null</tt> if none exist
     */
    public Collection<Cell> getSelectedCells(HintType hintType) {
        Cell[] cells = getSelectedCells();
        if (cells==null) return Collections.emptyList();
        return Arrays.asList(cells);
    }

    /**
     * Convenience method to be able to use arrays or Collections.
     * @return the cells that must be highlighted in a visual representation, or <tt>null</tt> if none exists
     */
    protected Cell[] getSelectedCells() {
    	return null;
    }

    /**
     * Get the regions concerned by this hint.
     * <tt>null</tt> can be returned if this hint does not depend on regions.
     * @return the regions concerned by this hint
     */
	public Region[] getRegions(HintType hintType) {
		return null;
	}

    /**
     * Get the pencil marks that were added and must be highlighted in a visual representation of this hint (green preferably).
     * @return the pencil marks that were added and must be highlighted in a visual representation, or <tt>null</tt> if none
     */
    public Map<Cell, BitSet> getAddedPotentials(HintType hintType, int viewNum) {
        return Collections.emptyMap();
    }
    
    /**
     * Get the pencil marks that were removed after the hint was applied
     * @return the pencil marks that were removed after applying the hint, or <tt>null</tt> if none exist
     * @see #getViewCount()
     */
    public abstract Map<ImmutableCell, BitSet> getRemovedPotentials(HintType hintType);
    
    /**
     * Get the pencil marks that must be highlighted in a visual representation of this hint.
     * @return the pencil marks that must be highlighted in a visual representation, or <tt>null</tt> if none
     * @see #getViewCount()
     */
    public Map<Cell, BitSet> getSelectedPotentials(HintType hintType, int viewNum) {
    	return Collections.emptyMap();
    }

    /**
     * Get the pencil marks that must be highlighted in a visual representation of this hint.
     * @return the pencil marks that must be highlighted in a visual representation, or <tt>null</tt> if none
     * @see #getViewCount()
     */
    public Map<Cell, BitSet> getSecondarySelectedPotentials(HintType hintType, int viewNum) {
    	return Collections.emptyMap();
    }
    
    /**
     * Get the links to draw in a visual representation of this hint.
     * @see #getViewCount()
     * @see Link
     */
    public Collection<Link> getLinks(HintType hintType, int viewNum) {
    	return Collections.emptyList();
    }

    /**
     * Get a string representation of this hint.
     * <p>
     * The string returned by this method is displayed in the hint tree.
     * @return a string representation of this hint
     */
    @Override
    public abstract String toString();

    /**
     * Returns how many different views the hint requires for visual representation.
     * Some chaining hints need more than one visual representation to prove the point.
     * Most of the getter methods support the numView parameter to return the view specific information
     */
    public int getViewCount(HintType hintType) {
        return 1;
    }

    /**
     * Get an HTML explanation of this hint, understandable by human beings.
     * @return an HTML explanation of this hint
     */
    public abstract String getHintHtml(HintType hintType);

    /**
     * Returns the number of steps this hint offers.
     * A normal hint comes in three steps (small clue, big clue, hint) but some hints
     * have just two or even one step (incorrect digits, solution hints, some warning hints etc.)
     */
	public int getNrOfSteps() {
		return 3;
	}

	public String getSortKey() {
		return String.format("%08d", sortKey) + toString(); //$NON-NLS-1$
	}
	
	public void setSortKey(long sortKey) {
		this.sortKey = sortKey;
	}

	/**
	 * Increments the number of times a hint was used for a certain HintType
	 */
	public void incHintUsage(HintType usage) {
		switch(usage) {
		case SMALL_CLUE: hintUsageSmallClue++; break; 
		case BIG_CLUE: hintUsageBigClue++; break; 
		case HINT: hintUsageHint++; break; 
		}
	}

	/**
	 * Returns the number of times a hint was used for a certain HintType
	 * @param usage
	 * @return number of times the hint was used for the specific HintType
	 */
	public int getHintUsage(HintType usage) {
		switch(usage) {
		case SMALL_CLUE: return hintUsageSmallClue; 
		case BIG_CLUE: return hintUsageBigClue; 
		case HINT: return hintUsageHint; 
		}
		return 0;
	}

}
